package com.color.admin.system.servie.impl;


import com.color.admin.common.core.base.AuthUser;
import com.color.admin.common.core.constant.Constant;
import com.color.admin.common.core.exception.BaseException;
import com.color.admin.common.core.page.PageRequest;
import com.color.admin.common.core.page.PageResponse;
import com.color.admin.common.core.utils.cache.RedisUtils;
import com.color.admin.common.core.utils.tools.QueryHelp;
import com.color.admin.system.entity.SysMenu;
import com.color.admin.system.entity.SysRole;
import com.color.admin.system.entity.query.MenuQueryCriteria;
import com.color.admin.system.entity.vo.menu.MenuTreeVo;
import com.color.admin.system.entity.vo.menu.MenuVo;
import com.color.admin.system.entity.vo.menu.MetaVo;
import com.color.admin.system.entity.vo.menu.RouterVo;
import com.color.admin.system.entity.vo.user.UserVo;
import com.color.admin.system.repository.MenuRepository;
import com.color.admin.system.servie.MenuService;
import com.color.admin.system.servie.RoleService;
import com.color.admin.system.servie.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author HaiQiu
 * @date 2021/12/23
 */
@Service
public class MenuServiceImpl implements MenuService {

    @Autowired
    private MenuRepository menuRepository;

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 顶级父级菜单
     */
    public static final String TOP_PARENT_ID = "0";
    public static final String BUTTON = "button";
    public static final String MENU = "menu";
    public static final String IFRAME = "iframe";
    public static final String LINK = "link";

    @Override
    public void save(SysMenu sysMenu) {
        sysMenu.setCreateTime(new Date());
        sysMenu.setUpdateTime(new Date());
        checkDefaultParams(sysMenu);
        menuRepository.save(sysMenu);
    }

    /**
     * 检测参数值
     *
     * @param sysMenu 菜单数据
     */
    private void checkDefaultParams(SysMenu sysMenu) {
        switch (sysMenu.getType()) {
            case MENU:
                sysMenu.setMethod(null);
                break;
            case IFRAME:
            case LINK:
                sysMenu.setMethod(null);
                sysMenu.setComponent(null);
                break;
            case BUTTON:
                sysMenu.setFullpage(false);
                sysMenu.setHiddenBreadcrumb(false);
                sysMenu.setRedirect(null);
                sysMenu.setComponent(null);
                break;
            default:
                throw new BaseException("菜单类型错误");
        }
    }

    @Override
    public void update(SysMenu sysMenu) {
        if (!StringUtils.hasText(sysMenu.getId())) {
            throw new BaseException(Constant.ID_EXITS);
        }
        SysMenu menu = menuRepository.findById(sysMenu.getId()).orElseGet(SysMenu::new);
        menu.setUpdateTime(new Date());
        menu.setMenuName(sysMenu.getMenuName());
        menu.setActive(sysMenu.getActive());
        menu.setIcon(sysMenu.getIcon());
        menu.setSort(sysMenu.getSort());
        menu.setType(sysMenu.getType());
        menu.setDescription(sysMenu.getDescription());
        menu.setParentId(sysMenu.getParentId());
        menu.setPath(sysMenu.getPath());
        menu.setHidden(sysMenu.getHidden());
        menu.setComponent(sysMenu.getComponent());
        menu.setValue(sysMenu.getValue());
        menu.setRedirect(sysMenu.getRedirect());
        menu.setFullpage(sysMenu.getFullpage());
        menu.setHiddenBreadcrumb(sysMenu.getHiddenBreadcrumb());
        menu.setMethod(sysMenu.getMethod());
        menu.setActiveUrl(sysMenu.getActiveUrl());
        //检测参数
        checkDefaultParams(menu);
        menuRepository.save(menu);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(Set<String> menuIds) {
        //数据校验
        menuIds.forEach(menuId -> {
            List<SysMenu> childMenus = menuRepository.findSysMenuByParentId(menuId);
            if (!CollectionUtils.isEmpty(childMenus)) {
                throw new BaseException("将要删除菜单包含子菜单，请先删除子集");
            }
        });
        //批量删除菜单和角色绑定关联
        menuRepository.deleteRolesMenusByMenuIds(menuIds);
        //执行删除操作
        if (menuRepository.deleteAllByIdIn(menuIds) == 0) {
            throw new BaseException(Constant.DELETE_FAIL);
        }
    }

    @Override
    public MenuVo get(String menuId) {
        SysMenu sysMenu = menuRepository.findById(menuId).orElseGet(SysMenu::new);
        MenuVo menuVo = new MenuVo();
        BeanUtils.copyProperties(sysMenu, menuVo);
        return menuVo;
    }

    @Override
    public PageResponse<MenuVo> list(PageRequest<MenuQueryCriteria> request) {
        MenuQueryCriteria params = request.getParams();
        Pageable pageable = org.springframework.data.domain.PageRequest.of(request.getOffset(), request.getPageSize());
        Page<SysMenu> page = menuRepository.findAll((root, query, criteriaBuilder) ->
                (QueryHelp.getPredicate(root, params, criteriaBuilder)), pageable);
        Page<MenuVo> menuVos = page.map(sysMenu -> {
                    MenuVo menuVo = new MenuVo();
                    BeanUtils.copyProperties(sysMenu, menuVo);
                    return menuVo;
                }
        );
        PageResponse<MenuVo> response = new PageResponse<>();
        response.setPageIndex(request.getPageIndex());
        response.setPageSize(request.getPageSize());
        response.setTotalElements(menuVos.getTotalElements());
        if (menuVos.getTotalElements() > 0) {
            response.setData(menuVos.getContent());
        }
        return response;
    }

    @Override
    public List<MenuVo> menuAll() {
        List<SysMenu> sysMenus = menuRepository.findAll();
        if (!CollectionUtils.isEmpty(sysMenus)) {
            List<MenuVo> menuVos = new ArrayList<>();
            sysMenus.forEach(sysMenu -> {
                MenuVo menuVo = new MenuVo();
                BeanUtils.copyProperties(sysMenu, menuVo);
                menuVos.add(menuVo);
            });
            return menuVos;
        }
        return null;
    }

    @Override
    public List<MenuTreeVo> tree() {
        List<SysMenu> sysMenus = menuRepository.findAll(Sort.by("sort"));
        //转换实体类型
        List<MenuTreeVo> menuTreeVoList = getMenuTreeVoList(sysMenus);
        //递归方法开始
        return getTreeVoList(menuTreeVoList);
    }

    @Override
    public List<RouterVo> getMenus(String userId) {
        List<SysMenu> menus = getMenusByUserId(userId);
        if (!CollectionUtils.isEmpty(menus)) {
            return getRouterList(menus);
        }
        return null;
    }

    @Override
    public List<AuthUser> authList(String userId) {
        List<SysMenu> sysMenus = getMenusByUserId(userId);
        List<AuthUser> authUsers = sysMenus.stream().filter(sysMenu -> sysMenu.getType().equals(BUTTON)).map(sysMenu -> {
            AuthUser authUser = new AuthUser();
            BeanUtils.copyProperties(sysMenu, authUser);
            authUser.setUserId(userId);
            return authUser;
        }).collect(Collectors.toList());
        if (redisUtils.getExpireTime(Constant.USER_AUTH + userId) != -2) {
            redisUtils.clearRedis(RedisUtils.clearType.MENU, userId);
        }
        redisUtils.set(Constant.USER_AUTH + userId, authUsers);
        return authUsers;
    }

    private List<SysMenu> getMenusByUserId(String userId) {
        if (userId == null) {
            throw new BaseException("获取用户ID失败");
        }
        UserVo userVo = userService.get(userId);
        List<SysMenu> menus = new ArrayList<>();
        if (userVo.getUsername().equals(Constant.ADMIN)) {
            menus = menuRepository.findAll();
        } else {
            Set<SysRole> roles = userService.findRolesByUserId(userId);
            if (!CollectionUtils.isEmpty(roles)) {
                Set<String> roleIds = roles.stream().map(SysRole::getId).collect(Collectors.toSet());
                menus = roleService.findMenusByRoleIds(roleIds);
            }
        }
        return menus;
    }

    private List<RouterVo> getRouterList(List<SysMenu> menus) {
        //排序
        menus.sort(Comparator.comparing(SysMenu::getUpdateTime).reversed());
        menus.sort(Comparator.comparing(SysMenu::getSort));
        //转换
        List<RouterVo> routerVos = conversionRouterVos(menus);
        //递归
        return getRouterVos(routerVos);
    }

    /**
     * 递归路由菜单开始
     *
     * @param routerVos 菜单集合
     * @return 路由菜单
     */
    private List<RouterVo> getRouterVos(List<RouterVo> routerVos) {
        List<RouterVo> routers = new ArrayList<>();
        routerVos.forEach(routerVo -> {
            if (routerVo.getParentId().equals(TOP_PARENT_ID) && !routerVo.getMeta().getType().equals(BUTTON)) {
                routers.add(getChildrenRouters(routerVo, routerVos));
            }
        });
        return routers;
    }


    /**
     * 实体对象转换
     *
     * @param menus 菜单那集合
     * @return 转换为RouterVo集合
     */
    private List<RouterVo> conversionRouterVos(List<SysMenu> menus) {
        List<RouterVo> routerVos = new ArrayList<>();
        for (SysMenu sysMenu : menus) {
            RouterVo routerVo = new RouterVo();

            routerVo.setPath(sysMenu.getPath());
            routerVo.setComponent(sysMenu.getComponent());
            routerVo.setRedirect(sysMenu.getRedirect());
            routerVo.setName(sysMenu.getValue());
            routerVo.setId(sysMenu.getId());
            routerVo.setParentId(sysMenu.getParentId());

            MetaVo metaVo = new MetaVo();
            metaVo.setActive(sysMenu.getActiveUrl());
            metaVo.setTitle(sysMenu.getMenuName());
            metaVo.setFullpage(sysMenu.getFullpage());
            metaVo.setHidden(sysMenu.getHidden());
            metaVo.setIcon(sysMenu.getIcon());
            metaVo.setType(sysMenu.getType());

            routerVo.setMeta(metaVo);
            routerVos.add(routerVo);
        }
        return routerVos;
    }

    /**
     * 获取路由菜单子集和
     *
     * @param vo        父级菜单
     * @param routerVos 所有菜单
     * @return 子集菜单和本身
     */
    private RouterVo getChildrenRouters(RouterVo vo, List<RouterVo> routerVos) {
        routerVos.forEach(child -> {
            if (child.getParentId().equals(vo.getId()) && !child.getMeta().getType().equals(BUTTON)) {
                vo.getChildren().add(getChildrenRouters(child, routerVos));
            }
        });
        return vo;
    }


    /**
     * 递归树形菜单开始
     *
     * @param menuTreeVoList 总集合
     * @return 属性菜单
     */
    private List<MenuTreeVo> getTreeVoList(List<MenuTreeVo> menuTreeVoList) {
        List<MenuTreeVo> finalTreeList = new ArrayList<>();
        menuTreeVoList.forEach(menuTreeVo -> {
            if (menuTreeVo.getParentId().equals(TOP_PARENT_ID)) {
                finalTreeList.add(getChildrenData(menuTreeVo, menuTreeVoList));
            }
        });
        return finalTreeList;
    }


    /**
     * 实体转换
     *
     * @param sysMenus 所有菜单
     * @return 转换MenuTreeVo
     */
    private List<MenuTreeVo> getMenuTreeVoList(List<SysMenu> sysMenus) {
        List<MenuTreeVo> treeVoList = sysMenus.stream().map(new Function<SysMenu, MenuTreeVo>() {
            @Override
            public MenuTreeVo apply(SysMenu sysMenu) {
                MenuTreeVo vo = new MenuTreeVo();
                BeanUtils.copyProperties(sysMenu, vo);
                return vo;
            }
        }).collect(Collectors.toList());
        return treeVoList;
    }

    /**
     * 递归获取子集
     *
     * @param vo  父级
     * @param vos 所有集合
     * @return 子集集合
     */
    private MenuTreeVo getChildrenData(MenuTreeVo vo, List<MenuTreeVo> vos) {
        vos.forEach(child -> {
            if (child.getParentId().equals(vo.getId())) {
                vo.getChildren().add(getChildrenData(child, vos));
            }
        });
        return vo;
    }

}
